﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Geometry
{
    /// <summary>
    /// Intersection Class providing Methods to Calulate Intersection points
    /// </summary>
    public static class Intersection
    {
        /// <summary>
        /// Check if the Point and line intersect
        /// </summary>
        /// <param name="l"></param>
        /// <param name="p"></param>
        /// <returns>True if the point is on the line</returns>
        private static bool LinePoint(Line l, Point p)
        {
            if (p.y == (l.a * p.x + l.b)) return true;
            else return false;
        }
        /// <summary>
        /// The line line intersection
        /// </summary>
        /// <param name="l1">First Line</param>
        /// <param name="l2">Second Line</param>
        /// <returns>Point of intersection or NULL if the lines are parralel</returns>
        private static Point LineLine(Line l1, Line l2)
        {
            if (Vector.AreParallel(l1.vec, l2.vec)) return null;
            double x = (l2.b - l1.b) / (l1.a - l2.a);
            double y = l1.a * x + l1.b;
            return new Point(x, y);
        }
        /// <summary>
        /// Line Circle Intersection
        /// </summary>
        /// <param name="l"></param>
        /// <param name="c"></param>
        /// <param name="second">If the second is true returns the second point looking by the line direction</param>
        /// <returns>Depends on the second paramter First or Second point of intersecion looking by the line direction vector
        /// NULL if there is no intersection</returns>
        private static Point LineCircle(Line l, Circle c, bool second = false)
        {
            double ea = 1 + Math.Pow(l.a, 2);
            double eb = -2 * c.center.x + 2 * l.a * l.b - 2 * l.a * c.center.y;
            double ec = Math.Pow(c.center.x, 2) + Math.Pow(l.b, 2) - 2 * l.b * c.center.y + Math.Pow(c.center.y, 2) - Math.Pow(c.radius, 2);
            QuadraticSolving solv = new QuadraticSolving(ea, eb, ec);
            solv.Calculate();
            if (solv.real_solutions == 0) return null;
            Point p1 = new Point();
            Point p2 = new Point();

#if DEBUG
            p1 = new Point(solv.x1, l.GetYFromX(solv.x1));
            p2 = new Point(solv.x2, l.GetYFromX(solv.x2));
            if (!Line.IsInLine(l, p1)) throw new SystemException("Punkt 1 nie lezy na prostej!");
            if (!Line.IsInLine(l, p2)) throw new SystemException("Punkt 2 nie lezy na prostej!");
#endif
            Vector points = new Vector(p1, p2);

            if (Vector.AreReversed(points, l.vec))
            {
                Point pt = new Point(p1);
                p1 = p2;
                p2 = pt;
            }

            if (second == false) return p1;
            return p2;
        }
        /// <summary>
        /// Circle Circle intersection
        /// </summary>
        /// <param name="c1"></param>
        /// <param name="c2"></param>
        /// <param name="second">If second parameter is true returns the point on the left of the c1.center->c2.center line</param>
        /// <returns>Depends on the second parameter the point on the left or on the right of the c1.center->c2.center line
        /// or NULL if there is no intersection</returns>
        private static Point CircleCircle(Circle c1, Circle c2, bool second = false)
        {
            double x1 = c1.center.x;
            double y1 = c1.center.y;
            double R1 = c1.radius;

            double x2 = c2.center.x;
            double y2 = c2.center.y;
            double R2 = c2.radius;

            double D = c2.center.Distance(c1.center);

            if (D == 0)
                return new Point();

            if (D == (R1 + R2))
            {
                Vector V = new Vector(c1.center, c2.center);
                V.SetLength((float)R1);
                Point P = new Point(V.EndPoint());
                return P;
            }

            if (D > (R1 + R2) || D < Math.Abs(R1 - R2))
                return new Point();

            double A = (D + R1 + R2) * (D + R1 - R2) * (D - R1 + R2) * (-D + R1 + R2);
            A = Math.Sqrt(A) / 4;

            double XE1 = (x1 + x2) / 2 - (x1 - x2) * (R1 * R1 - R2 * R2) / (2 * D * D);
            double XE2 = 2 * (y1 - y2) * A / (D * D);

            double YE1 = (y1 + y2) / 2 - (y1 - y2) * (R1 * R1 - R2 * R2) / (2 * D * D);
            double YE2 = 2 * (x1 - x2) * A / (D * D);

            Point pt1 = new Point((XE1 + XE2), (YE1 - YE2));
            Point pt2 = new Point((XE1 - XE2), (YE1 + YE2));
            Line l = new Line(c1.center, c2.center);
            if (second)
            {
                if (l.OnLeft(pt1)) return pt2;
                return pt1;
            }
            else
            {
                if (l.OnLeft(pt1)) return pt1;
                return pt2;
            }
        }

        public static Point GetIntersectionPoint(Geometry g1, Geometry g2, bool second = false)
        {
            if (g1.GetType() == Type.GetType("Line") && g2.GetType() == Type.GetType("Line")) return LineLine(g1 as Line, g2 as Line);
            if (g1.GetType() ==  Type.GetType("Line")&& g2.GetType() == Type.GetType("Circle")) return LineCircle(g1 as Line, g2 as Circle, second);
            if (g2.GetType() == Type.GetType("Line") && g1.GetType() == Type.GetType("Circle")) return LineCircle(g2 as Line, g1 as Circle, second);
            if (g1.GetType() == Type.GetType("Circle") && g2.GetType() == Type.GetType("Circle")) return CircleCircle(g1 as Circle, g2 as Circle);
            throw new SystemException("Wrong type for intersection");
        }
    }
}
